/*
    Author: Christopher Diggins
    License: MIT Licence 1.0
    Website: http://www.heron-language.com
*/

#ifndef HERON_TO_JAVA_HPP
#define HERON_TO_JAVA_HPP

/// Contains classes specialized for converting from Heron to Java
namespace heron2java
{
using namespace heron_ast;	

using namespace std;
using namespace yard;

void WriteStatement(Statement* node);

int nIndent = 0;
const char* outputPath = "";

/// Converts an AstNode pointer to a string
std::string NodeToStr(AstNode* pNode) {
    return pNode->ToString();
}

/// Outputs a single character
void Write(char c) {
    if (c == '\n') {
        fprintf(OUTPUT, "\n");
        for (int j=0; j < nIndent; ++j)
            fprintf(OUTPUT, "  ");
    }
    else {
        if (c == '{') 
            ++nIndent;
        if (c == '}')
            --nIndent;
        fputc(c, OUTPUT);
    }
}

/// Writes a sequence of 
template<typename Iter_T>
void Write(Iter_T begin, Iter_T end) {
    while (begin != end) {
        Write(*begin++);
    }
}

void Write(const std::string& s) {
    Write(s.begin(), s.end());
}

void WriteInt(int n) {
    fprintf(OUTPUT, "%d", n);
}

void WriteRawNode(AstNode* pNode) {
    Write(NodeToStr(pNode));
}

void WriteLine() {
    Write('\n');
}

void WriteLine(string s) {
    Write(s);
    WriteLine();
}

void WriteName(Name* x) {
    WriteRawNode(x);
}

void WriteExpr(Expr* expr) {
    WriteRawNode(expr);
}

template<typename NodeT, typename FunctionT>
void WriteNodeList(AstNodeList<NodeT>& nodes, const std::string& sep, FunctionT& func) {
    for (int i=0; i < nodes.Count(); ++i) {
        if (i > 0) 
            Write(sep);
        func(nodes.At(i));
    }
}

void WriteType(TypeExpr* type)  {
    if (type == NULL) {
        Write("Object");
    }
    else {
        std::string s = type->ToString();
        if (s == "Int") {
            s = "int";
        }    
        else if (s == "Array<Int>") {
            s = "int[]";
        }
        Write(s);
    }
}

void WriteArg(Arg* arg) {
    WriteType(arg->type);
    Write(" ");
    WriteName(arg->name);
}

void WriteArgList(ArgList* args) {
    Write("(");
    WriteNodeList(args->args, ", ", WriteArg);
    Write(")");
}

void WriteCodeBlock(CodeBlock* block) {
    WriteLine("{");
    WriteNodeList(block->statements, "\n", WriteStatement);
    WriteLine("}");
}

void WriteExprStatement(ExprStatement* x) {
    WriteExpr(x->expr);
    WriteLine(";");
}

void WriteReturnStatement(ReturnStatement* x) {
    Write("return ");
    WriteExpr(x->expr);
    WriteLine(";");
}

void WriteVarDecl(VarDecl* x) {
    WriteType(x->type);
    Write(" ");
    WriteName(x->name);
    if (x->expr != NULL) {
        Write(" = ");
        WriteExpr(x->expr);
    }
    WriteLine(";");
}

void WriteWhileStatement(WhileStatement* x) {
    Write("while (");
    WriteExpr(x->cond);
    WriteLine(")");
    WriteStatement(x->body);
}

void WriteForStatement(ForStatement* x) 
{
    // TODO: handle foreachindex statements. (or don't)
    Write("for (");
    WriteVarDecl(x->vardecl);
    WriteExpr(x->inv);
    Write("; ");
    WriteExpr(x->iter);
    WriteLine(")");
    WriteStatement(x->body);
}

void WriteForEachStatement(ForEachStatement* x) 
{
    Write("for (");
    WriteType(x->type);
    Write(" ");
    WriteName(x->name);
    Write(": ");
    WriteExpr(x->coll);
    WriteLine(")");
    WriteStatement(x->body);
}

void WriteIfStatement(IfStatement* x) {
    Write("if (");
    WriteExpr(x->cond);
    WriteLine(")");
    WriteStatement(x->body);
    if (x->alternative != NULL) {
        WriteLine("else");
        WriteStatement(x->alternative);
    }
}

void WriteStatement(Statement* x) {
    AstNode* child = x->statement;
    if (child->TypeMatches<ForStatement>())
        WriteForStatement(dynamic_cast<ForStatement*>(child));
    else if (child->TypeMatches<ForEachStatement>())
        WriteForEachStatement(dynamic_cast<ForEachStatement*>(child));
    else if (x->statement->TypeMatches<CodeBlock>()) 
        WriteCodeBlock(dynamic_cast<CodeBlock*>(child));
    else if (x->statement->TypeMatches<VarDecl>()) 
        WriteVarDecl(dynamic_cast<VarDecl*>(child));
    else if (x->statement->TypeMatches<IfStatement>()) 
        WriteIfStatement(dynamic_cast<IfStatement*>(child));
    else if (x->statement->TypeMatches<WhileStatement>()) 
        WriteWhileStatement(dynamic_cast<WhileStatement*>(child));
    else if (x->statement->TypeMatches<ReturnStatement>()) 
        WriteReturnStatement(dynamic_cast<ReturnStatement*>(child));
    else if (x->statement->TypeMatches<ExprStatement>()) 
        WriteExprStatement(dynamic_cast<ExprStatement*>(child));
    else 
        Error("Unrecognized statement type: " + x->ToString());
}    

void WriteFunction(Function* x) {
    Write("public static ");
    WriteType(x->type);
    Write(" ");
    WriteName(x->name);
    WriteArgList(x->args);
    WriteLine();
    WriteCodeBlock(x->body);
}

void WriteScript(ScriptFile* x, std::string name) {
    Write("class ");
    Write(name);
    WriteLine(" {");
    WriteNodeList(x->functions, "\n", WriteFunction);
    WriteLine("public static void main(String[] args) {");
    WriteLine("}");
    WriteLine("}");
}

} // namespace

#endif
